/*
  tasmota.ino - Tasmota firmware for iTead Sonoff, Wemos and NodeMCU hardware

  Copyright (C) 2020  Theo Arends

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/*********************************************************************************************\
 * Preferred IDE is Visual Studio Code with PlatformIO extension which doesn't need prerequisites
 *
 * Limited support for Arduino IDE needs Prerequisites:
 *    - Change libraries/PubSubClient/src/PubSubClient.h
 *       #define MQTT_MAX_PACKET_SIZE 1200
 *
 *  Arduino IDE 1.8.12 and up parameters for partly support
 *    - Select IDE Tools - Board: "Generic ESP8266 Module"
 *    - Select IDE Tools - Flash Mode: "DOUT (compatible)"
 *    - Select IDE Tools - Flash Size: "1M (FS:none OTA:~502KB)"
 *    - Select IDE Tools - LwIP Variant: "v2 Higher Bandwidth (no feature)"
 *    - Select IDE Tools - VTables: "Flash"
 *    - Select IDE Tools - Espressif FW: "nonos-sdk-2.2.1+100 (190703)"
\*********************************************************************************************/

// Location specific includes
#ifndef ESP32_STAGE                         // ESP32 Stage has no core_version.h file. Disable include via PlatformIO Option
#include <core_version.h>                   // Arduino_Esp8266 version information (ARDUINO_ESP8266_RELEASE and ARDUINO_ESP8266_RELEASE_2_7_1)
#endif
#include "tasmota_compat.h"
#include "tasmota_version.h"                // Tasmota version information
#include "tasmota.h"                        // Enumeration used in my_user_config.h
#include "my_user_config.h"                 // Fixed user configurable options
#ifdef USE_TLS
  #include <t_bearssl.h>                    // We need to include before "tasmota_globals.h" to take precedence over the BearSSL version in Arduino
#endif  // USE_TLS
#include "tasmota_globals.h"                // Function prototypes and global configuration
#include "i18n.h"                           // Language support configured by my_user_config.h
#include "tasmota_template.h"               // Hardware configuration

// Libraries
#include <ESP8266HTTPClient.h>              // Ota
#include <ESP8266httpUpdate.h>              // Ota
#include <StreamString.h>                   // Webserver, Updater
#include <JsonParser.h>
#include <JsonGenerator.h>
#ifdef USE_ARDUINO_OTA
  #include <ArduinoOTA.h>                   // Arduino OTA
  #ifndef USE_DISCOVERY
  #define USE_DISCOVERY
  #endif
#endif  // USE_ARDUINO_OTA
#ifdef USE_DISCOVERY
  #include <ESP8266mDNS.h>                  // MQTT, Webserver, Arduino OTA
#endif  // USE_DISCOVERY
//#ifdef USE_I2C
  #include <Wire.h>                         // I2C support library
//#endif  // USE_I2C
#ifdef USE_SPI
  #include <SPI.h>                          // SPI support, TFT
#endif  // USE_SPI

// Structs
#include "settings.h"

/*********************************************************************************************\
 * Global variables
\*********************************************************************************************/

WiFiUDP PortUdp;                            // UDP Syslog and Alexa

struct {
  uint32_t global_update;                   // Timestamp of last global temperature and humidity update
  uint32_t baudrate;                        // Current Serial baudrate
  uint32_t pulse_timer[MAX_PULSETIMERS];    // Power off timer
  uint32_t blink_timer;                     // Power cycle timer
  uint32_t backlog_delay;                   // Command backlog delay
  uint32_t loop_load_avg;                   // Indicative loop load average
  uint32_t web_log_index;                   // Index in Web log buffer
  uint32_t uptime;                          // Counting every second until 4294967295 = 130 year

  power_t power;                            // Current copy of Settings.power
  power_t rel_inverted;                     // Relay inverted flag (1 = (0 = On, 1 = Off))
  power_t last_power;                       // Last power set state
  power_t blink_power;                      // Blink power state
  power_t blink_powersave;                  // Blink start power save state
  power_t blink_mask;                       // Blink relay active mask

  int serial_in_byte_counter;               // Index in receive buffer

  float temperature_celsius;                // Provide a global temperature to be used by some sensors
  float humidity;                           // Provide a global humidity to be used by some sensors
  float pressure_hpa;                       // Provide a global pressure to be used by some sensors

  uint16_t gpio_pin[MAX_GPIO_PIN];          // GPIO functions indexed by pin number
  myio my_module;                           // Active copy of Module GPIOs (17 x 16 bits)
  uint16_t blink_counter;                   // Number of blink cycles
  uint16_t seriallog_timer;                 // Timer to disable Seriallog
  uint16_t syslog_timer;                    // Timer to re-enable syslog_level
  uint16_t tele_period;                     // Tele period timer
  int16_t save_data_counter;                // Counter and flag for config save to Flash
  RulesBitfield rules_flag;                 // Rule state flags (16 bits)

  bool serial_local;                        // Handle serial locally
  bool fallback_topic_flag;                 // Use Topic or FallbackTopic
  bool backlog_mutex;                       // Command backlog pending
  bool stop_flash_rotate;                   // Allow flash configuration rotation
  bool blinkstate;                          // LED state
  bool pwm_present;                         // Any PWM channel configured with SetOption15 0
  bool i2c_enabled;                         // I2C configured
  bool spi_enabled;                         // SPI configured
  bool soft_spi_enabled;                    // Software SPI configured
  bool ntp_force_sync;                      // Force NTP sync
  bool is_8285;                             // Hardware device ESP8266EX (0) or ESP8285 (1)
  bool skip_light_fade;                     // Temporarily skip light fading
  bool restart_halt;                        // Do not restart but stay in wait loop
  bool module_changed;                      // Indicate module changed since last restart

  StateBitfield global_state;               // Global states (currently Wifi and Mqtt) (8 bits)
  uint8_t blinks;                           // Number of LED blinks
  uint8_t restart_flag;                     // Tasmota restart flag
  uint8_t ota_state_flag;                   // OTA state flag
  uint8_t wifi_state_flag;                  // Wifi state flag
  uint8_t mqtt_cmnd_blocked;                // Ignore flag for publish command
  uint8_t mqtt_cmnd_blocked_reset;          // Count down to reset if needed
  uint8_t state_250mS;                      // State 250msecond per second flag
  uint8_t latching_relay_pulse;             // Latching relay pulse timer
  uint8_t active_device;                    // Active device in ExecuteCommandPower
  uint8_t sleep;                            // Current copy of Settings.sleep
  uint8_t leds_present;                     // Max number of LED supported
  uint8_t led_inverted;                     // LED inverted flag (1 = (0 = On, 1 = Off))
  uint8_t led_power;                        // LED power state
  uint8_t ledlnk_inverted;                  // Link LED inverted flag (1 = (0 = On, 1 = Off))
  uint8_t pwm_inverted;                     // PWM inverted flag (1 = inverted)
  uint8_t energy_driver;                    // Energy monitor configured
  uint8_t light_driver;                     // Light module configured
  uint8_t light_type;                       // Light types
  uint8_t serial_in_byte;                   // Received byte
  uint8_t devices_present;                  // Max number of devices supported
  uint8_t masterlog_level;                  // Master log level used to override set log level
  uint8_t seriallog_level;                  // Current copy of Settings.seriallog_level
  uint8_t syslog_level;                     // Current copy of Settings.syslog_level
  uint8_t module_type;                      // Current copy of Settings.module or user template type
  uint8_t last_source;                      // Last command source
  uint8_t shutters_present;                 // Number of actual define shutters
  uint8_t prepped_loglevel;                 // Delayed log level message

#ifndef SUPPORT_IF_STATEMENT
  uint8_t backlog_index;                    // Command backlog index
  uint8_t backlog_pointer;                  // Command backlog pointer
  String backlog[MAX_BACKLOG];              // Command backlog buffer
#endif

  char version[16];                         // Composed version string like 255.255.255.255
  char image_name[33];                      // Code image and/or commit
  char hostname[33];                        // Composed Wifi hostname
  char serial_in_buffer[INPUT_BUFFER_SIZE];  // Receive buffer
  char mqtt_client[99];                     // Composed MQTT Clientname
  char mqtt_topic[TOPSZ];                   // Composed MQTT topic
  char mqtt_data[MESSZ];                    // MQTT publish buffer and web page ajax buffer
  char log_data[LOGSZ];                     // Logging
  char web_log[WEB_LOG_SIZE];               // Web log buffer
} TasmotaGlobal;

#ifdef SUPPORT_IF_STATEMENT
  #include <LinkedList.h>
  LinkedList<String> backlog;               // Command backlog implemented with LinkedList
  #define BACKLOG_EMPTY (backlog.size() == 0)
#else
  #define BACKLOG_EMPTY (TasmotaGlobal.backlog_pointer == TasmotaGlobal.backlog_index)
#endif

/*********************************************************************************************\
 * Main
\*********************************************************************************************/

void setup(void) {
#ifdef ESP32
#ifdef DISABLE_ESP32_BROWNOUT
  DisableBrownout();      // Workaround possible weak LDO resulting in brownout detection during Wifi connection
#endif
#endif

  memset(&TasmotaGlobal, 0, sizeof(TasmotaGlobal));
  TasmotaGlobal.baudrate = APP_BAUDRATE;
  TasmotaGlobal.temperature_celsius = NAN;
  TasmotaGlobal.blinks = 201;
  TasmotaGlobal.wifi_state_flag = WIFI_RESTART;
  TasmotaGlobal.tele_period = 9999;
  TasmotaGlobal.active_device = 1;
  TasmotaGlobal.global_state.data = 0xF;  // Init global state (wifi_down, mqtt_down) to solve possible network issues

  RtcRebootLoad();
  if (!RtcRebootValid()) {
    RtcReboot.fast_reboot_count = 0;
  }
#ifdef FIRMWARE_MINIMAL
  RtcReboot.fast_reboot_count = 0;  // Disable fast reboot and quick power cycle detection
#else
  RtcReboot.fast_reboot_count++;
#endif
  RtcRebootSave();

  Serial.begin(TasmotaGlobal.baudrate);
//  Serial.setRxBufferSize(INPUT_BUFFER_SIZE);  // Default is 256 chars
  TasmotaGlobal.seriallog_level = LOG_LEVEL_INFO;  // Allow specific serial messages until config loaded

  snprintf_P(TasmotaGlobal.version, sizeof(TasmotaGlobal.version), PSTR("%d.%d.%d"), VERSION >> 24 & 0xff, VERSION >> 16 & 0xff, VERSION >> 8 & 0xff);  // Release version 6.3.0
  if (VERSION & 0xff) {  // Development or patched version 6.3.0.10
    snprintf_P(TasmotaGlobal.version, sizeof(TasmotaGlobal.version), PSTR("%s.%d"), TasmotaGlobal.version, VERSION & 0xff);
  }
  // Thehackbox inserts "release" or "commit number" before compiling using sed -i -e 's/PSTR("(%s)")/PSTR("(85cff52-%s)")/g' tasmota.ino
  snprintf_P(TasmotaGlobal.image_name, sizeof(TasmotaGlobal.image_name), PSTR("(%s)"), CODE_IMAGE_STR);  // Results in (85cff52-tasmota) or (release-tasmota)

  SettingsLoad();
  SettingsDelta();

  OsWatchInit();

  if (1 == RtcReboot.fast_reboot_count) {  // Allow setting override only when all is well
    UpdateQuickPowerCycle(true);
    XdrvCall(FUNC_SETTINGS_OVERRIDE);
  }

  TasmotaGlobal.seriallog_level = Settings.seriallog_level;
  TasmotaGlobal.seriallog_timer = SERIALLOG_TIMER;
  TasmotaGlobal.syslog_level = Settings.syslog_level;
  TasmotaGlobal.stop_flash_rotate = Settings.flag.stop_flash_rotate;  // SetOption12 - Switch between dynamic or fixed slot flash save location
  TasmotaGlobal.save_data_counter = Settings.save_data;
  TasmotaGlobal.sleep = Settings.sleep;
#ifndef USE_EMULATION
  Settings.flag2.emulation = 0;
#else
#ifndef USE_EMULATION_WEMO
  if (EMUL_WEMO == Settings.flag2.emulation) { Settings.flag2.emulation = 0; }
#endif
#ifndef USE_EMULATION_HUE
  if (EMUL_HUE == Settings.flag2.emulation) { Settings.flag2.emulation = 0; }
#endif
#endif  // USE_EMULATION

//  AddLogBuffer(LOG_LEVEL_DEBUG, (uint8_t*)&TasmotaGlobal, sizeof(TasmotaGlobal));

  if (Settings.param[P_BOOT_LOOP_OFFSET]) {         // SetOption36
    // Disable functionality as possible cause of fast restart within BOOT_LOOP_TIME seconds (Exception, WDT or restarts)
    if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET]) {       // Restart twice
      Settings.flag3.user_esp8285_enable = 0;       // SetOption51 - Enable ESP8285 user GPIO's - Disable ESP8285 Generic GPIOs interfering with flash SPI
      if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET] +1) {  // Restart 3 times
        for (uint32_t i = 0; i < MAX_RULE_SETS; i++) {
          if (bitRead(Settings.rule_stop, i)) {
            bitWrite(Settings.rule_enabled, i, 0);  // Disable rules causing boot loop
          }
        }
      }
      if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET] +2) {  // Restarted 4 times
        Settings.rule_enabled = 0;                  // Disable all rules
      }
      if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET] +3) {  // Restarted 5 times
        for (uint32_t i = 0; i < ARRAY_SIZE(Settings.my_gp.io); i++) {
          Settings.my_gp.io[i] = GPIO_NONE;         // Reset user defined GPIO disabling sensors
        }
      }
      if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET] +4) {  // Restarted 6 times
        Settings.module = Settings.fallback_module;  // Reset module to fallback module
//        Settings.last_module = Settings.fallback_module;
      }
      AddLog_P2(LOG_LEVEL_INFO, PSTR("FRC: " D_LOG_SOME_SETTINGS_RESET " (%d)"), RtcReboot.fast_reboot_count);
    }
  }

  Format(TasmotaGlobal.mqtt_client, SettingsText(SET_MQTT_CLIENT), sizeof(TasmotaGlobal.mqtt_client));
  Format(TasmotaGlobal.mqtt_topic, SettingsText(SET_MQTT_TOPIC), sizeof(TasmotaGlobal.mqtt_topic));
  if (strchr(SettingsText(SET_HOSTNAME), '%') != nullptr) {
    SettingsUpdateText(SET_HOSTNAME, WIFI_HOSTNAME);
    snprintf_P(TasmotaGlobal.hostname, sizeof(TasmotaGlobal.hostname)-1, SettingsText(SET_HOSTNAME), TasmotaGlobal.mqtt_topic, ESP_getChipId() & 0x1FFF);
  } else {
    snprintf_P(TasmotaGlobal.hostname, sizeof(TasmotaGlobal.hostname)-1, SettingsText(SET_HOSTNAME));
  }

  GetEspHardwareType();
  GpioInit();

//  SetSerialBaudrate(Settings.baudrate * 300);  // Allow reset of serial interface if current baudrate is different from requested baudrate

  WifiConnect();

  SetPowerOnState();

  AddLog_P2(LOG_LEVEL_INFO, PSTR(D_PROJECT " %s %s " D_VERSION " %s%s-" ARDUINO_CORE_RELEASE), PROJECT, SettingsText(SET_DEVICENAME), TasmotaGlobal.version, TasmotaGlobal.image_name);
#ifdef FIRMWARE_MINIMAL
  AddLog_P2(LOG_LEVEL_INFO, PSTR(D_WARNING_MINIMAL_VERSION));
#endif  // FIRMWARE_MINIMAL

  memcpy_P(TasmotaGlobal.log_data, VERSION_MARKER, 1);  // Dummy for compiler saving VERSION_MARKER

  RtcInit();

#ifdef USE_ARDUINO_OTA
  ArduinoOTAInit();
#endif  // USE_ARDUINO_OTA

  XdrvCall(FUNC_INIT);
  XsnsCall(FUNC_INIT);
#ifdef USE_SCRIPT
  if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">BS",3,0);
#endif

  TasmotaGlobal.rules_flag.system_init = 1;
}

void BacklogLoop(void) {
  if (TimeReached(TasmotaGlobal.backlog_delay)) {
    if (!BACKLOG_EMPTY && !TasmotaGlobal.backlog_mutex) {
      TasmotaGlobal.backlog_mutex = true;
      bool nodelay = false;
      bool nodelay_detected = false;
      String cmd;
      do {
#ifdef SUPPORT_IF_STATEMENT
        cmd = backlog.shift();
#else
        cmd = TasmotaGlobal.backlog[TasmotaGlobal.backlog_pointer];
        TasmotaGlobal.backlog[TasmotaGlobal.backlog_pointer] = (const char*) nullptr;  // Force deallocation of the String internal memory
        TasmotaGlobal.backlog_pointer++;
        if (TasmotaGlobal.backlog_pointer >= MAX_BACKLOG) { TasmotaGlobal.backlog_pointer = 0; }
#endif
        nodelay_detected = !strncasecmp_P(cmd.c_str(), PSTR(D_CMND_NODELAY), strlen(D_CMND_NODELAY));
        if (nodelay_detected) { nodelay = true; }
      } while (!BACKLOG_EMPTY && nodelay_detected);
      if (!nodelay_detected) {
        ExecuteCommand((char*)cmd.c_str(), SRC_BACKLOG);
      }
      if (nodelay) {
        TasmotaGlobal.backlog_delay = 0;  // Reset backlog_delay which has been set by ExecuteCommand (CommandHandler)
      }
      TasmotaGlobal.backlog_mutex = false;
    }
  }
}

void SleepDelay(uint32_t mseconds) {
  if (mseconds) {
    for (uint32_t wait = 0; wait < mseconds; wait++) {
      delay(1);
      if (Serial.available()) { break; }  // We need to service serial buffer ASAP as otherwise we get uart buffer overrun
    }
  } else {
    delay(0);
  }
}

void loop(void) {
  uint32_t my_sleep = millis();

  XdrvCall(FUNC_LOOP);
  XsnsCall(FUNC_LOOP);

  OsWatchLoop();

  ButtonLoop();
  SwitchLoop();
#ifdef USE_DEVICE_GROUPS
  DeviceGroupsLoop();
#endif  // USE_DEVICE_GROUPS
  BacklogLoop();

  static uint32_t state_50msecond = 0;               // State 50msecond timer
  if (TimeReached(state_50msecond)) {
    SetNextTimeInterval(state_50msecond, 50);
#ifdef ROTARY_V1
    RotaryHandler();
#endif  // ROTARY_V1
    XdrvCall(FUNC_EVERY_50_MSECOND);
    XsnsCall(FUNC_EVERY_50_MSECOND);
  }

  static uint32_t state_100msecond = 0;              // State 100msecond timer
  if (TimeReached(state_100msecond)) {
    SetNextTimeInterval(state_100msecond, 100);
    Every100mSeconds();
    XdrvCall(FUNC_EVERY_100_MSECOND);
    XsnsCall(FUNC_EVERY_100_MSECOND);
  }

  static uint32_t state_250msecond = 0;              // State 250msecond timer
  if (TimeReached(state_250msecond)) {
    SetNextTimeInterval(state_250msecond, 250);
    Every250mSeconds();
    XdrvCall(FUNC_EVERY_250_MSECOND);
    XsnsCall(FUNC_EVERY_250_MSECOND);
  }

  static uint32_t state_second = 0;                  // State second timer
  if (TimeReached(state_second)) {
    SetNextTimeInterval(state_second, 1000);
    PerformEverySecond();
    XdrvCall(FUNC_EVERY_SECOND);
    XsnsCall(FUNC_EVERY_SECOND);
  }

  if (!TasmotaGlobal.serial_local) { SerialInput(); }

#ifdef USE_ARDUINO_OTA
  ArduinoOtaLoop();
#endif  // USE_ARDUINO_OTA

  uint32_t my_activity = millis() - my_sleep;

  if (Settings.flag3.sleep_normal) {               // SetOption60 - Enable normal sleep instead of dynamic sleep
    //  yield();                                   // yield == delay(0), delay contains yield, auto yield in loop
    SleepDelay(TasmotaGlobal.sleep);                            // https://github.com/esp8266/Arduino/issues/2021
  } else {
    if (my_activity < (uint32_t)TasmotaGlobal.sleep) {
      SleepDelay((uint32_t)TasmotaGlobal.sleep - my_activity);  // Provide time for background tasks like wifi
    } else {
      if (TasmotaGlobal.global_state.network_down) {
        SleepDelay(my_activity /2);                // If wifi down and my_activity > setoption36 then force loop delay to 1/3 of my_activity period
      }
    }
  }

  if (!my_activity) { my_activity++; }             // We cannot divide by 0
  uint32_t loop_delay = TasmotaGlobal.sleep;
  if (!loop_delay) { loop_delay++; }               // We cannot divide by 0
  uint32_t loops_per_second = 1000 / loop_delay;   // We need to keep track of this many loops per second
  uint32_t this_cycle_ratio = 100 * my_activity / loop_delay;
  TasmotaGlobal.loop_load_avg = TasmotaGlobal.loop_load_avg - (TasmotaGlobal.loop_load_avg / loops_per_second) + (this_cycle_ratio / loops_per_second); // Take away one loop average away and add the new one
}
